/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { AstTreeUtils, ArkFile, ts } from "arkanalyzer";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Rule, Defects, FileMatcher, MatcherTypes, MatcherCallback } from '../../Index';
import { RuleListUtil } from "../../utils/common/DefectsList";
import { IssueReport } from "../../model/Defects";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoForInArrayCheck');
const gMetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/no-for-in-array-check.md", // TODO: support url
    description: "For-in loops over arrays skips holes, returns indices as strings, and may visit the prototype chain or other enumerable properties."
};

export class NoForInArrayCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private buildMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };
    public registerMatchers(): MatcherCallback[] {
        const matchBuildCb: MatcherCallback = {
            matcher: this.buildMatcher,
            callback: this.check.bind(this)
        };
        return [matchBuildCb];
    }
    public check = (arkFile: ArkFile) => {
        const isTsFile = this.isTsFile(arkFile.getFilePath());
        if (!isTsFile) {
            return;
        }
        const astRoot = AstTreeUtils.getASTNode(arkFile.getName(), arkFile.getCode());
        for (let child of astRoot.statements) {
            this.isForInCheck(child);
        }
    };
    private isForInCheck = (node: ts.Node): void => {
        if (ts.isForInStatement(node)) {
            this.handleForInStatement(node);
        }
        ts.forEachChild(node, this.isForInCheck);
    };
    private handleForInStatement(node: ts.ForInStatement): void {
        const iterationObject = node.expression;
        if (ts.isObjectLiteralExpression(iterationObject)) {
            return;
        }
        this.addIssueReport(node, this.metaData.description, this.metaData.severity);
    }
    private addIssueReport(node: ts.Node, description: string, severity: number): void {
        const warnInfo = this.getLineAndColumn(node);
        const defect = new Defects(warnInfo.line, warnInfo.startCol, warnInfo.endCol, description, severity, this.rule.ruleId,
            warnInfo.filePath, this.metaData.ruleDocPath, true, false, false, false);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }

    private getLineAndColumn(node: ts.Node): { line: number; startCol: number; endCol: number; filePath: string } {
        const sourceFile = node.getSourceFile();
        const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
        const endCharacter = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).character;
        return {
            line: line + 1,
            startCol: character + 1,
            endCol: endCharacter + 1,
            filePath: sourceFile.fileName
        };
    }
    private isTsFile(filePath: string): boolean {
        return filePath.toLowerCase().endsWith('.ts');
    }
}